 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal;

 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.IAction;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.ui.IActionDelegate;
 import org.eclipse.ui.IActionDelegate2;
 import org.eclipse.ui.IActionDelegateWithEvent;
 import org.eclipse.ui.INullSelectionListener;
 import org.eclipse.ui.IPluginContribution;
 import org.eclipse.ui.ISelectionListener;
 import org.eclipse.ui.IWorkbenchPart;
 import org.eclipse.ui.IWorkbenchWindowActionDelegate;
 import org.eclipse.ui.SelectionEnabler;
 import org.eclipse.ui.WorkbenchException;
 import org.eclipse.ui.internal.misc.StatusUtil;
 import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
 import org.eclipse.ui.internal.util.BundleUtility;
 import org.eclipse.ui.internal.util.Util;

 /**
  * A PluginAction is a proxy for an action extension.
  *
  * At startup we read the registry and create a PluginAction for each action extension.
  * This plugin action looks like the real action ( label, icon, etc ) and acts as
  * a proxy for the action until invoked. At that point the proxy will instantiate
  * the real action and delegate the run method to the real action.
  * This makes it possible to load the action extension lazily.
  *
  * Occasionally the class will ask if it is OK to
  * load the delegate (on selection changes). If the plugin containing
  * the action extension has been loaded then the action extension itself
  * will be instantiated.
  */

 public abstract class PluginAction extends Action implements
         ISelectionListener, ISelectionChangedListener, INullSelectionListener,
         IPluginContribution {
     private IActionDelegate delegate;

     private SelectionEnabler enabler;

     private ISelection selection;

     private IConfigurationElement configElement;

     private String pluginId;

     private String runAttribute = IWorkbenchRegistryConstants.ATT_CLASS;

     private static int actionCount = 0;

     /**
      * PluginAction constructor.
      *
      * @param actionElement the element
      * @param id the identifier
      * @param style the style bits
      */
     public PluginAction(IConfigurationElement actionElement, String id,
             int style) {
         super(null, style);

         this.configElement = actionElement;

         if (id != null) {
             setId(id);
         } else {
             // Create unique action id.
 setId("PluginAction." + Integer.toString(actionCount)); //$NON-NLS-1$
 ++actionCount;
         }

         String defId = actionElement
                 .getAttribute(IWorkbenchRegistryConstants.ATT_DEFINITION_ID);
         setActionDefinitionId(defId);

         pluginId = configElement.getNamespace();

         // Read enablement declaration.
 if (configElement.getAttribute(IWorkbenchRegistryConstants.ATT_ENABLES_FOR) != null) {
             enabler = new SelectionEnabler(configElement);
         } else {
             IConfigurationElement[] kids = configElement
                     .getChildren(IWorkbenchRegistryConstants.TAG_ENABLEMENT);
             IConfigurationElement[] kids2 = configElement
                     .getChildren(IWorkbenchRegistryConstants.TAG_SELECTION);
             if (kids.length > 0 || kids2.length>0) {
                 enabler = new SelectionEnabler(configElement);
             }
         }

         // Give enabler or delegate a chance to adjust enable state
 selectionChanged(new StructuredSelection());
     }

     /**
      * Creates the delegate and refreshes its enablement.
      */
     protected final void createDelegate() {
         // The runAttribute is null if delegate creation failed previously...
 if (delegate == null && runAttribute != null) {
             try {
                 Object obj = WorkbenchPlugin.createExtension(configElement,
                         runAttribute);
                 delegate = validateDelegate(obj);
                 initDelegate();
                 refreshEnablement();
             } catch (Throwable e) {
                 runAttribute = null;
                 IStatus status = null;
                 if (e instanceof CoreException) {
                     status = ((CoreException) e).getStatus();
                 } else {
                     status = StatusUtil
                             .newStatus(
                                     IStatus.ERROR,
                                     "Internal plug-in action delegate error on creation.", e); //$NON-NLS-1$
 }
                 String id = configElement.getAttribute(IWorkbenchRegistryConstants.ATT_ID);
                 WorkbenchPlugin
                         .log(
                                 "Could not create action delegate for id: " + id, status); //$NON-NLS-1$
 return;
             }
         }
     }

     /**
      * Validates the object is a delegate of the expected type. Subclasses can
      * override to check for specific delegate types.
      * <p>
      * <b>Note:</b> Calls to the object are not allowed during this method.
      * </p>
      *
      * @param obj a possible action delegate implementation
      * @return the <code>IActionDelegate</code> implementation for the object
      * @throws WorkbenchException if not of the expected delegate type
      */
     protected IActionDelegate validateDelegate(Object obj)
             throws WorkbenchException {
         if (obj instanceof IActionDelegate) {
             return (IActionDelegate) obj;
         }
         
         throw new WorkbenchException(
                 "Action must implement IActionDelegate"); //$NON-NLS-1$
 }

     /**
      * Initialize the action delegate by calling its lifecycle method.
      * Subclasses may override but must call this implementation first.
      */
     protected void initDelegate() {
         if (delegate instanceof IActionDelegate2) {
             ((IActionDelegate2) delegate).init(this);
         }
     }

     /**
      * Returns the action delegate if created. Can be <code>null</code>
      * if the delegate is not created yet or if previous delegate
      * creation failed.
      */
     protected IActionDelegate getDelegate() {
         return delegate;
     }

     /**
      * Returns true if the declaring plugin has been loaded
      * and there is no need to delay creating the delegate
      * any more.
      */
     protected boolean isOkToCreateDelegate() {
         if (getStyle() == IAction.AS_DROP_DOWN_MENU
                 && !WWinPluginPulldown.class.isInstance(this)) {
             return true;
         }
         
         // test if the plugin has loaded
 String bundleId = configElement.getNamespace();
         return BundleUtility.isActive(bundleId);
     }

     /**
      * Refresh the action enablement.
      */
     protected void refreshEnablement() {
         if (enabler != null) {
             setEnabled(enabler.isEnabledForSelection(selection));
         }
         if (delegate != null) {
             delegate.selectionChanged(this, selection);
         }
     }

     /* (non-Javadoc)
      * Method declared on IAction.
      */
     public void run() {
         runWithEvent(null);
     }

     /* (non-Javadoc)
      * Method declared on IAction.
      */
     public void runWithEvent(Event event) {
         // this message dialog is problematic.
 if (delegate == null) {
             createDelegate();
             if (delegate == null) {
                 MessageDialog
                         .openInformation(
                                 Util.getShellToParentOn(),
                                 WorkbenchMessages.Information,
                                 WorkbenchMessages.PluginAction_operationNotAvailableMessage);
                 return;
             }
             if (!isEnabled()) {
                 MessageDialog.openInformation(Util.getShellToParentOn(), WorkbenchMessages.Information,
                         WorkbenchMessages.PluginAction_disabledMessage);
                 return;
             }
         }

         if (event != null) {
             if (delegate instanceof IActionDelegate2) {
                 ((IActionDelegate2) delegate).runWithEvent(this, event);
                 return;
             }
             // Keep for backward compatibility with R2.0
 if (delegate instanceof IActionDelegateWithEvent) {
                 ((IActionDelegateWithEvent) delegate).runWithEvent(this, event);
                 return;
             }
         }

         delegate.run(this);
     }

     /**
      * Handles selection change. If rule-based enabled is
      * defined, it will be first to call it. If the delegate
      * is loaded, it will also be given a chance.
      *
      * @param newSelection the new selection
      */
     public void selectionChanged(ISelection newSelection) {
         // Update selection.
 selection = newSelection;
         if (selection == null) {
             selection = StructuredSelection.EMPTY;
         }

         // The selection is passed to the delegate as-is without
 // modification. If the selection needs to be modified
 // the action contributors should do so.

         // If the delegate can be loaded, do so.
 // Otherwise, just update the enablement.
 if (delegate == null && isOkToCreateDelegate()) {
             createDelegate();
         } else {
             refreshEnablement();
         }
     }

     /**
      * The <code>SelectionChangedEventAction</code> implementation of this
      * <code>ISelectionChangedListener</code> method calls
      * <code>selectionChanged(IStructuredSelection)</code> when the selection is
      * a structured one.
      */
     public void selectionChanged(SelectionChangedEvent event) {
         ISelection sel = event.getSelection();
         selectionChanged(sel);
     }

     /**
      * The <code>SelectionChangedEventAction</code> implementation of this
      * <code>ISelectionListener</code> method calls
      * <code>selectionChanged(IStructuredSelection)</code> when the selection is
      * a structured one. Subclasses may extend this method to react to the change.
      */
     public void selectionChanged(IWorkbenchPart part, ISelection sel) {
         selectionChanged(sel);
     }

     /**
      * For testing purposes only.
      *
      * @return the selection
      * @since 3.1
      */
     public ISelection getSelection() {
         return selection;
     }

     /**
      * Returns the action identifier this action overrides.
      * Default implementation returns <code>null</code>.
      *
      * @return the action identifier to override or <code>null</code>
      */
     public String getOverrideActionId() {
         return null;
     }

     /**
      * @return the IConfigurationElement used to create this PluginAction.
      *
      * @since 3.0
      */
     protected IConfigurationElement getConfigElement() {
         return configElement;
     }

     /* (non-Javadoc)
      * @see org.eclipse.ui.IPluginContribution#getLocalId()
      */
     public String getLocalId() {
         return getId();
     }

     /* (non-Javadoc)
      * @see org.eclipse.ui.IPluginContribution#getPluginId()
      */
     public String getPluginId() {
         return pluginId;
     }

     /**
      * Disposes the delegate, if created.
      *
      * @since 3.1
      */
     public void disposeDelegate() {
         // avoid calling dispose() twice if the delegate implements
 // both IActionDelegate2 and IWorkbenchWindowActionDelegate
 if (getDelegate() instanceof IActionDelegate2) {
             ((IActionDelegate2) getDelegate()).dispose();
         }
         else if (getDelegate() instanceof IWorkbenchWindowActionDelegate) {
             ((IWorkbenchWindowActionDelegate) getDelegate()).dispose();
         }
         delegate = null;
     }

     /**
      * Disposes this plugin action.
      *
      * @since 3.1
      */
     public void dispose() {
         disposeDelegate();
     }
 }

